package qy.jalgotrade.bar;

import java.io.Serializable;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import org.apache.commons.lang3.tuple.Pair;

import qy.jalgotrade.utils.PySpecs;

/**
 * @author qy
 *
 */
public class Bars implements PySpecs.ReadOnlyMap<String, Bar>, Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 4464884168826113684L;

	private final Map<String, Bar> __barDict;

	private final ZonedDateTime __dateTime;

	/**
	 * A group of :class:`Bar` objects.
	 * 
	 * @param barDict A map of instrument to :class:`Bar` objects.
	 * @throws Exception Exception
	 */
	public Bars(Map<String, Bar> barDict) throws Exception {

		if (barDict.size() == 0) {
			throw new Exception("No bars supplied");
		}

		// Check that bar datetimes are in sync
		ZonedDateTime firstDateTime = null;
		String firstInstrument = null;
		for (Map.Entry<String, Bar> e : barDict.entrySet()) {
			String instrument = e.getKey();
			Bar currentBar = e.getValue();
			if (firstInstrument == null) {
				firstDateTime = currentBar.getDateTime();
				firstInstrument = instrument;
			} else if (!currentBar.getDateTime().equals(firstDateTime)) {
				throw new Exception(String.format("Bar data times are not in sync. %s %s != %s %s", instrument,
				        currentBar.getDateTime(), firstInstrument, firstDateTime));
			}
		}

		this.__barDict = barDict;
		__dateTime = firstDateTime;
	}

	/**
	 * Returns the :class:`pyalgotrade.bar.Bar` for the given instrument. If the instrument is not found
	 * an exception is raised.
	 * 
	 * @param instrument instrument
	 * @return Bar
	 */
	@Override
	public Bar __getitem__(String instrument) {

		Bar aBar = __barDict.get(instrument);
		if (aBar == null) {
			throw new NoSuchElementException(String.format("\"%s\" does NOT exists!", instrument));
		} else {
			return aBar;
		}
	}

	/**
	 * Returns True if a :class:`pyalgotrade.bar.Bar` for the given instrument is available.
	 * 
	 * @param instrument instrument
	 * @return boolean
	 */
	@Override
	public boolean __contains__(String instrument) {

		return __barDict.containsKey(instrument);
	}

	@Override
	public Iterator<Pair<String, Bar>> __iter__() {

		return new Iterator<Pair<String, Bar>>() {

			final List<String> keys = keys();

			int i = 0;

			@Override
			public boolean hasNext() {

				return i < keys.size();
			}

			@Override
			public Pair<String, Bar> next() {

				if (hasNext()) {
					String key = keys.get(i++);
					Bar value = getBar(key);
					return Pair.of(key, value);
				} else {
					throw new NoSuchElementException("Iterator reached END!");
				}
			}
		};
	}

	@Override
	public int __len__() {

		return __barDict.size();
	}

	public List<Bar> items() {

		return new ArrayList<>(__barDict.values());
	}

	public List<String> keys() {

		return new ArrayList<>(__barDict.keySet());
	}

	/**
	 * Returns the instrument symbols.
	 * 
	 * @return
	 */
	public List<String> getInstruments() {

		return keys();
	}

	/**
	 * Returns the :class:`datetime.datetime` for this set of bars.
	 * 
	 * @return ZonedDateTime
	 */
	public ZonedDateTime getDateTime() {

		return __dateTime;
	}

	/**
	 * Returns the :class:`pyalgotrade.bar.Bar` for the given instrument or None if the instrument is
	 * not found.
	 * 
	 * @param instrument instrument
	 * @return Bar
	 */
	public Bar getBar(String instrument) {

		return __barDict.get(instrument);
	}

	public int getBarsNum() {

		return __barDict.size();
	}

	public Bar.Frequency getBarsFrequency() {

		List<Bar> items = items();
		if (items.size() > 0) {
			return items.get(0).getFrequency();
		} else {
			throw new IndexOutOfBoundsException("empty Bars object!");
		}
	}

	@Override
	public boolean equals(Object obj) {

		Bars rObj = (Bars) obj;
		return __barDict.equals(rObj.__barDict) && __dateTime.equals(rObj.__dateTime);
	}

	@Override
	public String toString() {

		StringBuilder sb = new StringBuilder();
		sb.append("Bars: {\n");
		for (String e : getInstruments()) {
			sb.append("    ");
			sb.append(((BasicBar) __barDict.get(e)).toString(e));
			sb.append("\n");
		}
		sb.append("}");
		return sb.toString();
	}
}
